%
% `Sky.tex' -- describes the sky rendering procedure
%
%  Written by Curtis Olson.  Started December, 1997.
%
% $Id: SGSky.tex,v 1.1.1.1 2002/09/07 02:58:20 curt Exp $
%------------------------------------------------------------------------


\documentclass[12pt]{article}

\usepackage{anysize}
\papersize{11in}{8.5in}
\marginsize{1in}{1in}{1in}{1in}

\usepackage{amsmath}

\usepackage{epsfig}

\usepackage{setspace}
\onehalfspacing

\usepackage{url}


\begin{document}


\title{
    SimGear \texttt{SGSky} class.
}


\author{
    Curtis Olson (\texttt{curt@flightgear.org})
}


\maketitle


\section{Introduction}

The \texttt{SGSky} class implements a blended sky dome, a haloed sun,
a textured moon with phase controllable by light direction, stars and
planets, and cloud layers.  \texttt{SGSky} is designed to be dropped
into existing plib based applications and depends heavily on plib's
scene graph library, ssg.  The sky implements various time of day
lighting effects, plays well with fog and visibility effects, and
implements cloud fly-through effects.  Additionally, you can wire in
the output of the \texttt{SGEphemeris} class to accurately position
all the objects in the sky.


\section{Synopsis}

\begin{verbatim}
#include <simgear/sky/sky.hxx>

SGSky thesky();
\end{verbatim}


\section{\texttt{SGSky}}

\subsection{Constructor}

\begin{verbatim}
SGSky thesky();
\end{verbatim}

This creates an instance of the \texttt{SGSky} object.  Once you
create an instance of this class you need to call the \texttt{build()}
method to specify the parameters of your sky pieces and build all the
internal structures.  This could be merged in with the constructor
call, but it isn't at this point.

\subsection{Building the sky}

Once you have created an instance of \texttt{SGSky} you must call the
\texttt{build()} method.  However, building the sky requires several
textures.  So, you must specify the path to these textures before
building the sky by calling the \texttt{texture\_path()} method.

\begin{verbatim}
void texture_path( const string& path )
\end{verbatim}

\begin{verbatim}
void build( double sun_size, double moon_size,
            int nplanets, sgdVec3 *planet_data, double planet_dist,
            int nstars, sgdVec3 *star_data, double star_dist );
\end{verbatim}

The arguments allow you to specify the size of your sun sphere and
moon sphere.  You can specify the number of planets, an array of right
ascensions, declinations, and magnitudes, and the distance from the
view point to place the planets.  You can specify the same information
for the stars.

\subsection{Cloud Layers}

Cloud layers can be added, changed, or removed individually.  To add a
cloud layer use the \texttt{add\_cloud\_layer()} method:

\begin{verbatim}
void add_cloud_layer( double asl, double thickness,
                      double transition, double span,
                      SGCloudType type );
\end{verbatim}

The arguments allow you to specify base height above sea level, layer
thickness, a transition zone for entering/leaving the cloud layer, the
size of the cloud object, and the type of cloud texture.  All
distances are in meters.  There are additional forms of this method
that allow you to specify your own \texttt{ssgSimpleState} or texture
name for drawing the cloud layer.

\subsection{Repainting the Sky}

As the sun circles the globe, you can ask that the sky objects be
repainted to simulate sunrise and sunset effects, visibility, and
other lighting changes.

\begin{verbatim}
bool repaint( sgVec4 sky_color, sgVec4 fog_color,
              double sun_angle, double moon_angle,
              int nplanets, sgdVec3 *planet_data,
              int nstars, sgdVec3 *star_data );
\end{verbatim}

The arguments allow you to specify a base sky color (for the top of
the dome), a fog color (for the horizon), the sun angle with the
horizon (for sunrise/sunset effects), the moon angle (so we can make
it more yellow at the horizon), and new star and planet data so that
we can optionally change the magnitude of these (for day / night
transitions.)

\subsection{Repositioning Sky Objects}

As time progresses and as you move, the apparent position of the
objects and the various lighting effects can change. the
\texttt{reposition()} method allows you to specify the positions of
all the sky objects as well as your view position.

\begin{verbatim}
bool reposition( sgVec3 view_pos, sgVec3 zero_elev, sgVec3 view_up,
                 double lon, double lat, double alt, double spin,
                 double gst, 
                 double sun_ra, double sun_dec, double sun_dist,
                 double moon_ra, double moon_dec, double moon_dist );
\end{verbatim}

The arguments allow you to specify your view position in world
Cartesian coordinates, the zero elevation position in world Cartesian
coordinates (your longitude, your latitude, sea level), the ``up''
vector in world Cartesian coordinates, current longitude, latitude,
and altitude.  A ``spin'' angle can be specified for orienting the sky
with the sun position so sunset and sunrise effects look correct.  You
must specify GMT side real time, the sun right ascension, sun
declination, and sun distance from view point (to keep it inside your
view volume.)  You also must specify moon right ascension, moon
declination, and moon distance from view point.

\subsection{Rendering the Sky}

The sky is designed to be rendered in two stages.  The first stage
renders the stuff that forms your back drop -- the sky dome, the stars
and planets, the sun, and the moon.  This should be rendered before
the rest of your scene.  The second stage renders the clouds which are
likely to be translucent (depending on type) and should be drawn after
your scene has been rendered.

A typical application might do the following:

\begin{verbatim}
  thesky->preDraw();
  ssgCullAndDraw ( myscene ) ;
  thesky->postDraw( my_altitude );
\end{verbatim}

The current altitude in meters is passed to the \texttt{postDraw()}
method so the clouds layers can be rendered correction from most
distant to closest.

\subsection{Visibility Effects}

Visibility and fog is important for correctly rendering the sky.  You
can inform \texttt{SGSky} of the current visibility by calling the
\texttt{set\_visibility()} method.

When transitioning through clouds, it is nice to pull in the fog as
you get close to the cloud layer to hide the fact that the clouds are
drawn as a flat polygon.  As you get nearer to the cloud layer it is
also nice to temporarily pull in the visibility to simulate the
effects of flying in and out of the puffy edge of the cloud.  These
effects can all be accomplished by calling the \texttt{modify\_vis()}
method:

\begin{verbatim}
void modify_vis( float alt, float time_factor );
\end{verbatim}

The arguments allow you to specify your current altitude (which is
then compared to the altitudes of the various cloud layers.)  You can
also specify a time factor which should be the length in seconds since
the last time you called \texttt{modify\_vis()}.  The
\texttt{time\_factor} value allows the puffy cloud effect to be
calculated correctly.

The \texttt{modify\_vis()} method alters the \texttt{SGSky}'s internal
idea of visibility, so you should subsequently call
\texttt{get\_visibility()} to get the actual modified visibility.  You
should then make the appropriate \texttt{glFog()} calls to setup fog
properly for your scene.

\subsection{Accessor Methods}

Once an instance of \texttt{SGSky} has been successfully initialized,
there are a couple accessor methods you can use:

\begin{verbatim}
int get_num_layers();
\end{verbatim}

Returns the number of defined cloud layers

\begin{verbatim}
SGCloudLayer *get_cloud_layer( int i );
\end{verbatim}

Returns the $i^{th}$ cloud layer.

\begin{verbatim}
float get_visibility();
\end{verbatim}

Returns the current visibility as modified by the \texttt{SGSky} instance.


\section{Implementation}

This sections describes how the sky has been implemented in OpenGL.

\subsection{Overview}

The sky is represent as a 12 sided dome (or upside down bowl if you
prefer.)  Figure \ref{fig:dome} shows how a 6 sided dome might be
constructed.

\begin{figure}[hbt]
  \centerline{                   
      \psfig{file=dome.eps}
  }
  \caption{Simplified (6 Sided) Sky Dome}
  \label{fig:dome}
\end{figure}

The center section can be constructed with a triangle fan.  The inner
and outer ``skirts'' can be constructed with triangle strips.

The colors of each vertex can be independently controlled to achieve
sky to haze transitions, sunrise/sunset effects with a pinkish/oranges
glow, and one side of the sky can easily be made brighter than the
other.  By enabling smooth shading in OpenGL, the colors will be
blended together for a very nice effect.


\subsection{Vertex Generation}

The sky dome structure is intended to be centered over the current
view point at sea level.  So, we simply have to the appropriate ssg
structures and use the transformation node to position and orient it.

\subsection{Vertex Coloring}

For each vertex position array, there is a corresponding vertex color
array.  By being able to individually control the color at each
vertex, we can do all sorts of nice sky to haze blending with dusk and
dawn effects.  For complete details, use the source, Luck.  However,
here's a quick overview.

\subsubsection{Day and Night Coloring}

For the general middle of the day, or middle of the night sky, we
already know the desired sky color, and the haze color.  This is
computed elsewhere based on the current sun position.  During the
night these colors are both nearly black.  During the dawn they are
smoothly transitioned to day time colors.  And, during the dusk they
are smoothly transitioned back to night time colors.

The center of the dome is assigned the current sky color.  The color
of the first inner ring of vertices is weighted 70\% towards the sky
color and 30\% towards the fog color.

Then color of the middle ring of vertices is weighted 10\% towards the
sky color and 90\% towards the fog color.

The the outer ring of vertices are assigned the current fog color.

\subsubsection{Dusk and Dawn Effects}

Dusk and dawn effects can be accomplished by controlling the color of
the vertices.  Rather than trying to figure out which vertices are
near the current sun position, I just rotate the dome so the 0'th
vertex of each ring (and the center fan) align with the sun.  This
makes it easier to calculate vertex colors.  But, there is a fair
amount of work involved in calculating the proper dome rotation.

\begin{figure}[hbt]
  \centerline{                   
      \psfig{file=earth.eps}
  }
  \caption{Overview of Earth}
  \label{fig:earth}
\end{figure}

Figure \ref{fig:earth} shows an overview of the setup.  $P$, the
current view position, and $\mathbf{n}$, the local ``up'' vector,
define the plane which is tangent to the Earth's surface at point $P$.
Just for a quick review of your linear algebra, given $\mathbf{v_0}$,
the position vector of $P$ and $\mathbf{v}$, the position vector of
some other arbitrary point on the plane, and $\mathbf{n}$, the normal
to the plane, then the vector $\mathbf{n}$ and the vector $(\mathbf{v}
- \mathbf{v_0})$ are orthogonal (perpendicular.)  If the two vectors
are orthogonal then their dot product will be zero, so the following
must be true:

\begin{equation}
  \mathbf{n} \cdot ( \mathbf{v} - \mathbf{v_0} ) = 0
\end{equation}

This is the vector equation of the plane and can be rewritten as:

\begin{align}
  a(x - x_0) + b(y - y_0) + c(z - z_0) &= 0 \\
  ax + by + cz - (\mathbf{n} \cdot \mathbf{v_0}) &= 0
\end{align}

We want to find a vector $\mathbf{v}$ representing the
direction along the current tangent plane towards the position on the
Earth where the Sun is directly overhead.  The vector $\mathbf{u}$ is
defined as $\vec{\mathbf{PS}}$.

\begin{figure}[hbt]
  \centerline{                   
      \psfig{file=local.eps}
  }
  \caption{Vectors and Points in Local Coordinate System}
  \label{fig:local}
\end{figure}

Figure \ref{fig:local} shows a more detailed ``local'' view of the
points and vectors involved.  The point, $P$, is the current view
point.  The vector, $\mathbf{n}$, is the local up vector.  $S$
represents the current position on the Earth's surface where the Sun
is directly overhead.  We want to find the vector, $\mathbf{v}$ which
is a projection of $\mathbf{u}$ onto the plane defined by $P$ and
$\mathbf{n}$.

To do this we first calculate $\mathbf{u_1}$ which is the shortest
distance from point $S$ to the tangent plane.

\begin{equation}
  \mathbf{u_1} = \frac { \mathbf{n} \cdot \mathbf{u} }
             { {\| \mathbf{n} \|}^2 } \mathbf{n}
\end{equation}

Armed with $\mathbf{u_1}$ we can now calculate
$\mathbf{v}$ which is the local surface direction on the tangent
plane towards the sun, $S$.

\begin{equation}
  \mathbf{v} = \mathbf{v_0} + \mathbf{u} - \mathbf{u_1}
\end{equation}

Ok, so now we have $\mathbf{v}$, but the fun doesn't stop here.  Now
we need to calculate a rotation angle $\theta$ about $\mathbf{n}$ to
align our dome with $\mathbf{v}$.  The origin of the dome always
aligns with a vector pointing directly South.  So, we need to repeat
the above procedure to map a vector pointing straight down $( 0, 0,
-\mathbf{z} )$ onto our tangent plane to produce the local, surface,
south vector $\mathbf{w}$.  We then take the $\arccos()$ of the dot product
of $\mathbf{v}$ with $\mathbf{w}$.

\begin{equation}
  \theta = \arccos( \mathbf{v} \cdot \mathbf{w} )
\end{equation}

Whew, that gives us the angle we want.  Well almost, not quite.  The
problem is that the dot product returns a number in the range of
$(-1.0 \ldots 1.0)$. Thus, the $\arccos()$ function returns a $\theta$
in the range of $(0.0 \ldots 180.0)$.  But this is not enough
information to determine if $\mathbf{v}$ is in the east hemisphere or
west hemisphere and if this angle should be positive or negative.

So, to get that last piece of information we need, we can rotate the
vector $\mathbf{w}$ by 90 degrees about $\mathbf{n}$.  This gives us
the local surface east vector on the tangent plane.  Taking the dot
product of $\mathbf{v}$ and the local east vector tells us which
hemisphere $\mathbf{v}$ is in.  And, from this, we can uniquely
determine the proper angle for the sky dome rotation.

\end{document}


%------------------------------------------------------------------------
